package com.supsi.segreteria.client;

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.*;

/**
 * Created with IntelliJ IDEA.
 * User: fabio
 * Date: 18.05.12
 * Time: 16:42
 * To change this template use File | Settings | File Templates.
 */

/**
 * ClientCommunication engine with the server. All commands and protocol logic is implemented in
 * this singletone class
 * Singletone class
 */
public class ClientCommunicationManager implements Runnable, IClientSubject {
    private static ClientCommunicationManager instance = null;
    private ArrayList<String> messageFromServer;
    private ConnectionClient connectionClient;
    private List observers;
    private String state;
    private boolean mIsConnected;

    /**
     * Instantiation of singleton class
     * @return new class if not instantiated or previous if already instantiated
     */
    public static ClientCommunicationManager getInstance() {
        if (instance == null) {
            instance = new ClientCommunicationManager();
        }
        return instance;
    }

    /**
     * private Constructor for singleton class
     */
    private ClientCommunicationManager() {
        observers = new ArrayList();
        state = "";
        messageFromServer=new ArrayList<String>();
        mIsConnected = false;
    }

    /**
     * connection to server address
     * change the internal state for notification
     * @param ipAddress string ip address of the server
     * @return boolean if connected or not
     */
    public boolean connectToServer(String ipAddress) {
        mIsConnected = false;
        connectionClient = new ConnectionClient(ipAddress);
        try {
            connectionClient.connect();
            mIsConnected = true;
            state= "CONNECTED";
        } catch (UnknownHostException e) {
            //e.printStackTrace();
            mIsConnected = false;
            state= "Connection error";
        } catch (IOException e) {
            //e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            mIsConnected = false;
            state= "Connection error";
        }
        return mIsConnected;
    }

    /**
     * close connection to the server
     * change the internal state for notification
     * @return string server response of the close request
     */
    public String disconnectFromServer() {
        String rc ="";
        mIsConnected = false;

        try {
            connectionClient.sendMessage("EXIT");
            rc = connectionClient.getServerAck();
            connectionClient.close();
            state= "CLOSED";
        } catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            state = "Connection LOST";
        }

        return rc;
    }

    /**
     * send hello to server to identificate
     * @param myNicName nicName for the connection
     * @return string server response of the hello command
     */
    public String welcomeMessage(String myNicName){
        String msg="";

        try {
            connectionClient.sendMessage("HELLO " + myNicName);
            msg = connectionClient.getServerAck();
            if (msg.startsWith("NEWMESSAGES")) state = "NEWMESSAGES";
        }
        catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            state = "Connection LOST";
            mIsConnected = false;
        }
        return msg;
    }

    /**
     * check for new messages
     * if found a NOTIFY from server, change internal state
     */
    public void checkMessages(){
        if (connectionClient.notifyFromServer())
        {
            state = "NOTIFY";
        }
    }

    /**
     * Send a new message to user: destinationNicName
     * @param destinationNickName destination user to leave a message
     * @param message message to send
     * @return string server response of the messageto command
     *
     */
    public String messageTo(String destinationNickName, String message){
        String msg="";

        try {
            connectionClient.sendMessage("MESSAGETO " + destinationNickName + " "+ message);
            msg = connectionClient.getServerAck();
            state = "MESSAGETO";
        }
        catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            state = "Connection LOST";
            mIsConnected = false;
        }
        return msg;
    }

    /**
     * retreive all messages waiting on the server
     * @param messagesToRetreive String telling if you want only the "NEW" messages or "ALL" messages, others discarded
     * @return messageFromServer array[] of type Message containing all messages retreived. Empty if no messages.
     *
     */
    public Message[] getMessages(MessagesToRetreive messagesToRetreive){

        ArrayList<Message> messageStorage = new ArrayList<Message>();
        try{
            switch (messagesToRetreive){
                case NEW:
                    connectionClient.sendMessage("GETMESSAGES NEW");
                    break;
                case ALL:
                    connectionClient.sendMessage("GETMESSAGES ALL");
                    break;
            }

            messageFromServer = connectionClient.getMessages();
            // parse dei messaggi ignorando i messaggi di risposta del server non conformi a strutture Message
            for (String line : messageFromServer){
                if (line.contains("|"))   {
                    Message msg;
                    String[] singleMessage= {"","",""};
                    StringTokenizer st = new StringTokenizer(line, "|");
                    while(st.hasMoreTokens()) {
                        singleMessage[0] = st.nextToken();
                        singleMessage[1] = st.nextToken();
                        singleMessage[2] = st.nextToken();
                    }
                    //singleMessage = line.split("|");
                    msg = new Message(singleMessage[0].trim(), singleMessage[2].trim(), singleMessage[1].trim());
                    messageStorage.add(msg);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            state = "Connection LOST";
            mIsConnected = false;
        }
        Message[] arMsg = new Message[messageStorage.size()];
        messageStorage.toArray(arMsg); //prende la destinazione come parametro e ci copia dentro gli elementi
        return arMsg;
    }

    /**
     * delete message number "messageNumber"
     *     if messageNumber equals to 0 delete all messages
     * @param messageNumber
     * @return server response
     */
    public String deleteMessage(int messageNumber){
        String msg="";
        try {
            if (messageNumber==0){
                connectionClient.sendMessage("DELMESSAGES ALL");
            } else {
                connectionClient.sendMessage("DELMESSAGES " + Integer.toString(messageNumber));
            }
            state = "DELMESSAGE";
            msg = connectionClient.getServerAck();
        } catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            state = "Connection LOST";
            mIsConnected = false;
        }
        return msg;
    }

    /**
     * main thread loop
     * notify observers when internal state changes
     * periodically check the server for new messages during connection
     */
    @Override
    public void run() {
        String previousState = state;

        while (true) {
            if (state != previousState){
                // notify observers
                notifyObservers();
                state ="";
            }
            // just to check if thread is working fine
            //GregorianCalendar gc = new GregorianCalendar();
            //System.out.println(gc.get(GregorianCalendar.SECOND) + state);
            Thread t = Thread.currentThread();
            try {
                //thread to sleep for the specified number of milliseconds
                t.sleep(5000);
            } catch (Exception e) {
                System.out.println(e);
            }
            checkMessages();
        }

    }

    /**
     * used from observers to know in which state is the clientConnectionManager
     * @return
     */
    public String getState() {
        return state;
    }

    /**
     * Add observers to the class
     * @param o
     */
    public void addObserver(IClientObserver o) {
        observers.add(o);
    }

    /**
     * remove observers
     * @param o
     */
    public void removeObserver(IClientObserver o) {
        observers.remove(o);
    }

    /**
     * notify observers added to the class
     */
    public void notifyObservers() {
        Iterator i = observers.iterator();
        while (i.hasNext()) {
            IClientObserver o = (IClientObserver) i.next();
            o.update(this);
        }
    }
}
